package com.mai4j.maiadmin.misc;

import com.mai4j.maiadmin.event.pojo.SqlLog;
import com.mai4j.maiadmin.model.UserPrincipal;
import com.mai4j.maiadmin.support.CtxProvider;
import com.mai4j.maiadmin.support.security.JwtAuthentication;
import com.mai4j.maiadmin.support.security.SecurityUtils;
import com.mai4j.maiadmin.support.web.Client;
import com.mai4j.maiadmin.support.web.ClientInfo;
import com.p6spy.engine.common.P6Util;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;

/**
 * 微信公众号请关注: 开源螺丝钉
 * <br>
 * 码云请关注: <a href="https://gitee.com/xiyoufang">https://gitee.com/xiyoufang</a>
 * <br>
 * 哔哩哔哩请关注: <a href="https://space.bilibili.com/438927834">https://space.bilibili.com/438927834</a>
 *
 * @author xiyoufang
 */
public class AuditableSpyLogAppender implements MessageFormattingStrategy {

    public static class MockAuthentication extends AbstractAuthenticationToken {

        public MockAuthentication() {
            super(AuthorityUtils.NO_AUTHORITIES);
        }

        @Override
        public Object getCredentials() {
            return null;
        }

        @Override
        public Object getPrincipal() {
            return "mock/anonymous";
        }
    }

    private final ClientInfo mockClientInfo;
    private final MockAuthentication mockAuthentication;

    public AuditableSpyLogAppender() {
        mockClientInfo = new ClientInfo()
                .setIp("0.0.0.0")
                .setForwardedIp("0.0.0.0")
                .setUserAgent("MockClient/0.0");
        mockAuthentication = new MockAuthentication();
    }

    /**
     * Formats a log message for the logging module
     *
     * @param connectionId the id of the connection
     * @param now          the current ime expressing in milliseconds
     * @param elapsed      the time in milliseconds that the operation took to complete
     * @param category     the category of the operation
     * @param prepared     the SQL statement with all bind variables replaced with actual values
     * @param sql          the sql statement executed
     * @param url          the database url where the sql statement executed
     * @return the formatted log message
     */
    @Override
    public String formatMessage(final int connectionId, final String now, final long elapsed, final String category, final String prepared, final String sql, final String url) {
        String singled = P6Util.singleLine(prepared);
        ClientInfo clientInfo = Optional.ofNullable(Client.get()).orElse(mockClientInfo);
        Authentication authentication = Optional.ofNullable(SecurityUtils.getAuthentication()).orElse(mockAuthentication);
        CtxProvider.ctx().publishEvent(new SqlLog()
                .setAppName(CtxProvider.getAppName())
                .setVisitor(getVisitor(authentication))
                .setIp(clientInfo.getIp())
                .setForwardedIp(clientInfo.getForwardedIp())
                .setUserAgent(clientInfo.getUserAgent())
                .setConnection(connectionId)
                .setElapsed(elapsed)
                .setSqlText(singled)
                .setSqlDate(LocalDateTime.parse(now, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))));
        return elapsed + "ms |connection " + connectionId + "|" + singled;
    }

    /**
     * 获取访问数据库授权信息
     *
     * @param authentication authentication
     * @return visitor
     */
    private String getVisitor(Authentication authentication) {
        if (authentication instanceof MockAuthentication) {
            return (String) authentication.getPrincipal();
        }
        if (authentication instanceof AnonymousAuthenticationToken) {
            return "anonymous";
        }
        if (authentication instanceof JwtAuthentication) {
            UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
            return principal.getAccount();
        }
        return "unknown";
    }
}